
#pragma once

#include <functional>
#include <atomic>
#include <condition_variable>
#include <mutex>
#include <queue>
#include <future>
#include <thread>

class threadpool
{
    using task_t = std::function<void()>;
public:
    explicit threadpool(std::size_t size = std::max(2u, std::thread::hardware_concurrency()));

    threadpool(threadpool const &) = delete;
    threadpool &operator=(threadpool const &) = delete;

    ~threadpool();
public:
#if defined(__cpp_lib_invoke) && __cpp_lib_invoke >= 201411L
    template<typename F, typename...Args>
    auto submit(F &&f, Args&&...args) -> 
        std::future<decltype(std::invoke(std::forward<F>(f), std::forward<Args>(args)...))>
    {
        if (_stop) {
            std::runtime_error("Submit a task on a stopped thread pool.");
        }

        using return_type = decltype(std::invoke(std::forward<F>(f), std::forward<Args>(args)...));

        std::function<return_type()> function = std::bind(std::forward<F>(f), std::forward<Args>(args)...);
        auto task = std::make_shared<std::packaged_task<return_type()>>(std::move(function));

        std::unique_lock<std::mutex> ul(_mutex);
        _tasks.emplace([task]() {
            std::invoke(*task);
        });
        ul.unlock();
        _cv.notify_one();

        return task->get_future();
    }
#else // defined(__cpp_lib_invoke) && __cpp_lib_invoke >= 201411L
    template<typename F, typename...Args>
    auto submit(F &&f, Args&&...args) -> 
        std::future<decltype(std::forward<F>(f)(std::forward<Args>(args)...))>
    {
        if (_stop) {
            std::runtime_error("Submit a task on a stopped thread pool.");
        }

        using return_type = decltype(std::forward<F>(f)(std::forward<Args>(args)...));

        std::function<return_type()> function = std::bind(std::forward<F>(f), std::forward<Args>(args)...);
        auto task = std::make_shared<std::packaged_task<return_type()>>(std::move(function));

        std::unique_lock<std::mutex> ul(_mutex);
        _tasks.emplace([task]() {
            (*task)();
        });
        ul.unlock();
        _cv.notify_one();

        return task->get_future();
    }

    template<typename F, typename T, typename...Args>
    auto submit(F &&f, T &&obj, Args&&...args) -> 
        std::future<decltype((std::forward<T>(obj)->*f)(std::forward<Args>(args)...))>
    {
        if (_stop) {
            std::runtime_error("Submit a task on a stopped thread pool.");
        }

        using return_type = decltype((std::forward<T>(obj)->*f)(std::forward<Args>(args)...));

        std::function<return_type()> function = std::bind(std::forward<F>(f), std::forward<T>(obj), 
            std::forward<Args>(args)...);
        auto task = std::make_shared<std::packaged_task<return_type()>>(std::move(function));

        std::unique_lock<std::mutex> ul(_mutex);
        _tasks.emplace([task]() {
            (*task)();
        });
        ul.unlock();
        _cv.notify_one();

        return task->get_future();
    }

    template<typename F, typename T, typename...Args>
    auto submit(F &&f, T &&obj, Args&&...args) -> 
        std::future<decltype((std::forward<T>(obj).*f)(std::forward<Args>(args)...))>
    {
        if (_stop) {
            std::runtime_error("Submit a task on a stopped thread pool.");
        }

        using return_type = decltype((std::forward<T>(obj).*f)(std::forward<Args>(args)...));

        std::function<return_type()> function = std::bind(std::forward<F>(f), std::forward<T>(obj), 
            std::forward<Args>(args)...);
        auto task = std::make_shared<std::packaged_task<return_type()>>(std::move(function));

        std::unique_lock<std::mutex> ul(_mutex);
        _tasks.emplace([task]() {
            (*task)();
        });
        ul.unlock();
        _cv.notify_one();

        return task->get_future();
    }
#endif // defined(__cpp_lib_invoke) && __cpp_lib_invoke >= 201411L

    //void threshold(std::size_t threshold);

    //std::size_t threshold();

    std::size_t size() const;

    std::size_t idle() const;

    std::size_t busy() const;

    static std::shared_ptr<threadpool> global();

    static std::shared_ptr<threadpool> create(std::size_t size = std::max(2u, std::thread::hardware_concurrency()));
private:
    void worker();
    // void daemon();
private:
    /* 任务队列 */
    std::queue<task_t> _tasks;
    /* 线程队列 */
    std::vector<std::unique_ptr<std::thread>> _threads;
    /* 守护线程 */
    // std::unique_ptr<std::thread> _daemon;
    /* 线程阈值 */
    // std::size_t _threshold;
    /* 线程数量 */
    std::size_t _size;
    /* 是否运行 */
    std::atomic<bool> _stop;
    /* 空闲线程数量 */
    std::atomic<std::size_t> _idle;
    /* 条件变量 */
    std::condition_variable _cv;
    /* 互斥变量 */
    std::mutex _mutex;
    /* 全局实例 */
    static std::shared_ptr<threadpool> _global;
};
